﻿using AppUpdateInstaller.Model;
using AppUpdateInstaller.Model.InstallationStages;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System;
using System.Threading.Tasks;
using TestUtilities;

namespace AppUpdateInstaller.UnitTests.ModelTests
{
	[TestClass]
	public class CompositeStageTests
	{
		private Mock<IInstallationStage> mockStage1;
		private Mock<IInstallationStage> mockStage2;

		private ActionMock<IInstallationStage, InstallationStageProgressEventArgs> mockProgressChangeHandler;

		private CompositeStage compositeStage;

		[TestInitialize]
		public void InitializeTest()
		{
			this.mockStage1 = new Mock<IInstallationStage>();
			this.mockStage2 = new Mock<IInstallationStage>();

			this.mockProgressChangeHandler = new ActionMock<IInstallationStage, InstallationStageProgressEventArgs>();

			this.compositeStage = new CompositeStage(new[] { InstallationStage1, InstallationStage2 });
		}

		[TestMethod]
		[ExpectedException(typeof(ArgumentNullException))]
		public void Constructor_ThrowsForNullInstallationStageList()
		{
			new CompositeStage(null);
		}

		[TestMethod]
		public void HasFinalizerStage_FalseIfBothStagesHasNoFinalizerStage()
		{
			Assert.IsFalse(this.compositeStage.HasFinalizerStage);
		}

		[TestMethod]
		public void HasFinalizerStage_TrueIfAtLeastOneOfTheStagesHasFinalizerStage()
		{
			this.mockStage1
				.SetupGet(sp => sp.HasFinalizerStage)
				.Returns(true);

			Assert.IsTrue(this.compositeStage.HasFinalizerStage);
		}

		[TestMethod]
		public void SupportsRollback_FalseIfNoneOfStagesSupportsRollback()
		{
			Assert.IsFalse(this.compositeStage.SupportsRollback);
		}

		[TestMethod]
		public void SupportsRollback_TrueIfAtLeastOneOfTheStagesSupportsRollback()
		{
			this.mockStage2
				.SetupGet(sp => sp.SupportsRollback)
				.Returns(true);

			Assert.IsTrue(this.compositeStage.SupportsRollback);
		}

		[TestMethod]
		public void Run_CallsRunOnUnderlyingStages()
		{
			this.compositeStage.Run();

			this.mockStage1.Verify(st => st.Run(), Times.Once());
			this.mockStage2.Verify(st => st.Run(), Times.Once());
		}

		[TestMethod]
		public void RunAsync_CallsRunOnUnderlyingStagesAsynchronousely()
		{
			this.compositeStage.RunAsync().Wait();

			this.mockStage1.Verify(st => st.Run(), Times.Once());
			this.mockStage2.Verify(st => st.Run(), Times.Once());
		}

		[TestMethod]
		public void Rollback_CallsRollbackOfStagesWhichSupportIt()
		{
			this.mockStage1
				.SetupGet(st => st.SupportsRollback)
				.Returns(true);

			this.compositeStage.RollBack();

			this.mockStage1.Verify(st => st.RollBack(), Times.Once());
			this.mockStage2.Verify(st => st.RollBack(), Times.Never());
		}

		[TestMethod]
		public void RollbackAsync_CallsRollbackOnStagesWhichSupportIt()
		{
			this.mockStage2
				.SetupGet(st => st.SupportsRollback)
				.Returns(true);

			this.compositeStage.RollBackAsync().Wait();

			this.mockStage1.Verify(st => st.RollBack(), Times.Never());
			this.mockStage2.Verify(st => st.RollBack(), Times.Once());
		}

		[TestMethod]
		public void FinalizeStage_CallsFinalizeStageOnStagesWhichSupportIt()
		{
			this.mockStage2
				.SetupGet(st => st.HasFinalizerStage)
				.Returns(true);

			this.compositeStage.FinalizeStage();

			this.mockStage1.Verify(st => st.FinalizeStage(), Times.Never());
			this.mockStage2.Verify(st => st.FinalizeStage(), Times.Once());
		}

		#region Properties
		private IInstallationStage InstallationStage1
		{
			get
			{
				return this.mockStage1.Object;
			}
		}

		private IInstallationStage InstallationStage2
		{
			get
			{
				return this.mockStage2.Object;
			}
		}

		private Action<IInstallationStage, InstallationStageProgressEventArgs> ProgressChangeHandler
		{
			get
			{
				return this.mockProgressChangeHandler.Action;
			}
		}
		#endregion
	}
}
